/*
 * File     : LargeParameter32.java
 * Created  : 1 May 2011
 *
 * Copyright © 2011 Matthew Wilson (mj. {my-surname} .uk {at} gmail.com)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.googlecode.dni.internal.call;

import static com.googlecode.dni.internal.DniTypes.LIBRARY_HELPER_TYPE;
import static org.objectweb.asm.Opcodes.*;

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;

import com.googlecode.dni.internal.DniInternal;
import com.googlecode.dni.internal.structure.StructureBuilder;


/**
 * Special parameter implementation for passing parameters through a buffer.
 * Used when the number of parameters exceeds a limit.  All parameters are
 * passed as 32-bit (and 64-bit as two 32-bit).
 *
 * @author Matthew Wilson
 */
final class LargeParameter32 extends Parameter
{

    private final List< Parameter > parameters = new ArrayList< Parameter >();

    private final int words;

    private int scratchVarIndex;

    private Label scratchVarStart;


    /**
     * @param parameters
     *            the parameters to send
     */
    LargeParameter32( final List< Parameter > parameters )
    {
        this.parameters.addAll( parameters );

        int wordsTemp = 0;
        for ( Parameter parameter : parameters )
        {
            if ( parameter.getNativeType() == 'J' ||
                 parameter.getNativeType() == 'D' )
            {
                wordsTemp++;
            }
            wordsTemp++;
        }
        this.words = wordsTemp;

        setNativeTypeAsPointer();
        assert getNativeType() == 'I';
    }

    /**
     * @return the number of 32-bit words used
     */
    int getWords()
    {
        return this.words;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    int getJavaVarSlots()
    {
        int slots = 0;
        for ( Parameter parameter : this.parameters )
        {
            slots += parameter.getJavaVarSlots();
        }
        return slots;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    int allocatePrepareVarSlots( final MethodVisitor methodVisitor, final int varCount )
    {
        this.scratchVarIndex = varCount;

        if ( DniInternal.DEBUG_MODE )
        {
            this.scratchVarStart = new Label();
            methodVisitor.visitLabel( this.scratchVarStart );
        }

        methodVisitor.visitInsn( ACONST_NULL );
        methodVisitor.visitVarInsn( ASTORE, this.scratchVarIndex );

        int slots = 1;
        for ( Parameter parameter : this.parameters )
        {
            slots += parameter.allocatePrepareVarSlots( methodVisitor, varCount + slots );
        }

        return slots;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    void writePrepareParameter( final MethodVisitor methodVisitor )
    {
        for ( Parameter parameter : this.parameters )
        {
            parameter.writePrepareParameter( methodVisitor );
        }

        StructureBuilder.writeConstantInt( methodVisitor, this.words * 4 );
        methodVisitor.visitMethodInsn( INVOKESTATIC,
                                       LIBRARY_HELPER_TYPE,
                                       "reserveScratch",
                                       "(I)Ljava/lang/Object;" );
        methodVisitor.visitVarInsn( ASTORE, this.scratchVarIndex );

        methodVisitor.visitVarInsn( ALOAD, this.scratchVarIndex );
        StructureBuilder.writeConstantInt( methodVisitor, this.words * 4 );
        methodVisitor.visitMethodInsn( INVOKESTATIC,
                                       LIBRARY_HELPER_TYPE,
                                       "getScratchBuffer",
                                       "(Ljava/lang/Object;I)Ljava/nio/ByteBuffer;" );

        for ( Parameter parameter : this.parameters )
        {
            parameter.writeGetParameter( methodVisitor );

            switch ( parameter.getNativeType() )
            {
                case 'Z':
                case 'S':
                case 'C':
                case 'I':
                    methodVisitor.visitMethodInsn( INVOKEVIRTUAL,
                                                   "java/nio/ByteBuffer",
                                                   "putInt",
                                                   "(I)Ljava/nio/ByteBuffer;" );
                    break;

                case 'J':
                    methodVisitor.visitMethodInsn( INVOKEVIRTUAL,
                                                   "java/nio/ByteBuffer",
                                                   "putLong",
                                                   "(J)Ljava/nio/ByteBuffer;" );
                    break;

                case 'F':
                    // TODO Varargs passes float as double !!!
                    methodVisitor.visitMethodInsn( INVOKEVIRTUAL,
                                                   "java/nio/ByteBuffer",
                                                   "putFloat",
                                                   "(F)Ljava/nio/ByteBuffer;" );
                    break;

                case 'D':
                    methodVisitor.visitMethodInsn( INVOKEVIRTUAL,
                                                   "java/nio/ByteBuffer",
                                                   "putDouble",
                                                   "(D)Ljava/nio/ByteBuffer;" );
                    break;

                default:
                    throw new AssertionError( parameter.getNativeType() );
            }
        }

        methodVisitor.visitInsn( POP );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    void writeGetParameter( final MethodVisitor methodVisitor )
    {
        methodVisitor.visitVarInsn( ALOAD, this.scratchVarIndex );
        StructureBuilder.writeConstantInt( methodVisitor, this.words * 4 );
        methodVisitor.visitMethodInsn( INVOKESTATIC,
                                       LIBRARY_HELPER_TYPE,
                                       "getScratchPointer",
                                       "(Ljava/lang/Object;I)J" );

        methodVisitor.visitInsn( L2I );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    boolean needsTidying()
    {
        return true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    void writeTidyParameter( final MethodVisitor methodVisitor )
    {
        StructureBuilder.writeConstantInt( methodVisitor, this.words * 4 );
        methodVisitor.visitMethodInsn( INVOKESTATIC,
                                       LIBRARY_HELPER_TYPE,
                                       "releaseScratch",
                                       "(I)V" );


        for ( ListIterator< Parameter > it = this.parameters.listIterator( this.parameters.size() );
              it.hasPrevious(); )
        {
            it.previous().writeTidyParameter( methodVisitor );
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    void writeReturnParameter( final MethodVisitor methodVisitor, final int returnVarIndex )
    {
        throw new UnsupportedOperationException();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    void finishedMethod( final MethodVisitor methodVisitor,
                         final Label methodStart )
    {
        if ( DniInternal.DEBUG_MODE )
        {
            Label scratchVarEnd = new Label();
            methodVisitor.visitLabel( scratchVarEnd );

            methodVisitor.visitLocalVariable( "argumentScratch",
                                              "Ljava/lang/Object;",
                                              null,
                                              this.scratchVarStart,
                                              scratchVarEnd,
                                              this.scratchVarIndex );
        }

        for ( Parameter parameter : this.parameters )
        {
            parameter.finishedMethod( methodVisitor, methodStart );
        }
    }

}
